﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;

namespace ArtMath.Animation.Helper
{
    /// <summary>
    /// 高精度计时器
    /// </summary>
    public class HighPerformanceTimer
    {
        public class ElapsedEventArgs : EventArgs
        {
            public int TimerCount { get; private set; }
            public long ElapsedMicroseconds { get; private set; }
            public long TimerLateBy { get; private set; }
            public long CallbackFunctionExecutionTime { get; private set; }
            public ElapsedEventArgs(int timerCount, long elapsedMicroseconds, long timerLateBy, long callbackFunctionExecutionTime)
            {
                TimerCount = timerCount;
                ElapsedMicroseconds = elapsedMicroseconds;
                TimerLateBy = timerLateBy;
                CallbackFunctionExecutionTime = callbackFunctionExecutionTime;
            }
        }
        class StopWatchX : Stopwatch
        {
            public long ElapsedMicroseconds
            {
                get
                {
                    return ElapsedTicks * 1000000 / Frequency;
                }
            }
        }
        public delegate void ElapsedEventHandler(object sender, ElapsedEventArgs e);
        public event ElapsedEventHandler Elapsed;
        Thread threadTimer = null;
        long ignoreEventIfLateBy = long.MaxValue, timerIntervalInMicroSec = 0;
        bool stopTimer = true;
        public HighPerformanceTimer(long timerIntervalInMicroseconds)
        {
            Interval = timerIntervalInMicroseconds;
        }
        public HighPerformanceTimer()
        {

        }
        public long Interval
        {
            get
            {
                return Interlocked.Read(ref timerIntervalInMicroSec);
            }
            set
            {
                Interlocked.Exchange(ref timerIntervalInMicroSec, value);
            }
        }
        public long IgnoreEventIfLateBy
        {
            get
            {
                return Interlocked.Read(ref ignoreEventIfLateBy);
            }
            set
            {
                Interlocked.Exchange(ref ignoreEventIfLateBy, value <= 0 ? long.MaxValue : value);
            }
        }
        public bool IsRunning
        {
            get
            {
                return threadTimer != null && threadTimer.IsAlive;
            }
        }
        public void Start()
        {
            if (IsRunning || Interval <= 0) return;
            stopTimer = false;
            threadTimer = new Thread(() =>
            {
                NotificationTimer(ref timerIntervalInMicroSec, ref ignoreEventIfLateBy, ref stopTimer);
            })
            {
                IsBackground = true
            };
            threadTimer.Priority = ThreadPriority.Highest;
            threadTimer.Start();
        }
        public void Stop()
        {
            Abort();
            stopTimer = true;
        }
        public void StopAndWait()
        {
            StopAndWait(Timeout.Infinite);
        }
        public bool StopAndWait(int timeoutInMilliSec)
        {
            stopTimer = true;
            if (!IsRunning || threadTimer.ManagedThreadId == Thread.CurrentThread.ManagedThreadId)
            {
                return true;
            }
            return threadTimer.Join(timeoutInMilliSec);
        }
        public void Abort()
        {
            stopTimer = true;
            if (IsRunning)
            {
                threadTimer.Abort();
            }
        }
        void NotificationTimer(ref long timerIntervalInMicroSec, ref long ignoreEventIfLateBy, ref bool stopTimer)
        {
            int timerCount = 0;
            long nextNotification = 0;
            StopWatchX sw = new StopWatchX();
            sw.Start();
            while (!stopTimer)
            {
                long callbackFunctionExecutionTime = sw.ElapsedMicroseconds - nextNotification,
                    timerIntervalInMicroSecCurrent = Interlocked.Read(ref timerIntervalInMicroSec),
                    ignoreEventIfLateByCurrent = Interlocked.Read(ref ignoreEventIfLateBy);
                nextNotification += timerIntervalInMicroSecCurrent;
                timerCount++;
                long elapsedMicroseconds = 0;
                while ((elapsedMicroseconds = sw.ElapsedMicroseconds) < nextNotification)
                {
                    Thread.SpinWait(10);
                }
                long timerLateBy = elapsedMicroseconds - nextNotification;
                if (timerLateBy >= ignoreEventIfLateByCurrent)
                {
                    continue;
                }
                Elapsed(this, new ElapsedEventArgs(timerCount, elapsedMicroseconds, timerLateBy, callbackFunctionExecutionTime));
            }
            sw.Stop();
        }
    }
}
